Skip to content

Add protected resource metadata response inspect & modify support#1262

Open
FICTURE7 wants to merge 1 commit intomodelcontextprotocol:mainfrom
FICTURE7:fallback
Open

Add protected resource metadata response inspect & modify support#1262
FICTURE7 wants to merge 1 commit intomodelcontextprotocol:mainfrom
FICTURE7:fallback

Conversation

@FICTURE7
Copy link

This patch adds support for legacy behavior defined in MCP 2025-03-26, where by if the MCP server does not provide a PRM, the MCP client uses the MCP server's base URL as the authorization server.

Since a PRM is not specified, a resource indicator is also not specified, this means the code has to updated to handle this optionality.

Motivation and Context

There exists multiple MCP server implementation out there, and many of them are not compliant with the latest MCP specification. For example, the official Atlassian MCP server.

Without this patch, the C# MCP SDK is not compatible with Atlassian's MCP server (https://mcp.atlassian.com/v1/mcp), and would throw an McpException with Failed to find protected resource metadata at a well-known location for {...}.

if (metadata is null)
{
throw new McpException($"Failed to find protected resource metadata at a well-known location for {_serverUrl}");
}

On the other hand, the TypeScript MCP SDK seems to handle this case well.

https://github.com/modelcontextprotocol/typescript-sdk/blob/91f6a277c43babe8962c7782030162530a82b2f5/packages/client/src/client/auth.ts#L410-L416

How Has This Been Tested?

Performed a successful authentication against Atlassian's MCP server.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@stephentoub stephentoub requested a review from halter73 February 11, 2026 22:47
}
}

private static Uri GetRequiredResourceUri(ProtectedResourceMetadata protectedResourceMetadata)
Copy link
Contributor

@halter73 halter73 Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to keep this validation particularly for the non-legacy OAuth flow where the authorization server and resource server are hosted separately.

8. Resource Parameter Implementation

MCP clients MUST implement Resource Indicators for OAuth 2.0 as defined in RFC 8707
to explicitly specify the target resource for which the token is being requested. The resource parameter:

  1. MUST be included in both authorization requests and token requests.
  2. MUST identify the MCP server that the client intends to use the token with.
  3. MUST use the canonical URI of the MCP server as defined in RFC 8707 Section 2.

https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#resource-parameter-implementation

The spec mandates this, because otherwise the OAuth server has no way to know what MCP server the client plans to connect to using the access token it's given (since the MCP server URI and redirect URI are completely independent) and therefore could not prevent a phishing attack causing the client to send tokens to an untrusted MCP server. This is explained in https://den.dev/blog/mcp-authorization-resource/.

You can find previous discussion about this at #1041 (comment)

This may not be an issue specifically for the legacy flow where the resource server and the authorization are one and the same, so there's no risk of sending a token to a malicious resource server, but this is shared code.

This kind of issue is why we've been hesitant to support the legacy OAuth flow, and why cohosting the OAuth server and the resource server implicitly is no longer part of the official MCP spec.

Frankly, I'm surprised Atlassian hasn't just hosted a PRM JSON document at https://mcp.atlassian.com/.well-known/oauth-protected-resource yet. It could point at the existing cohosted OAuth server.

https://community.atlassian.com/forums/Rovo-questions/Remote-MCP-server-well-known-protected-endpoint-missing/qaq-p/3182723

Given that Atlassian has the only major MCP server requiring OAuth that I'm aware of without a PRM JSON document, I'm hesitant to add extra logic to the ClientOAuthProvider just for that. I wonder if writing a DelegatingHandler to create a fake /.well-known/oauth-protected-resource document might be a reasonable workaround.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frankly, I'm surprised Atlassian hasn't just hosted a PRM JSON document at https://mcp.atlassian.com/.well-known/oauth-protected-resource yet. It could point at the existing cohosted OAuth server.

Likewise 👀

Given that Atlassian has the only major MCP server requiring OAuth that I'm aware of without a PRM JSON document, I'm hesitant to add extra logic to the ClientOAuthProvider just for that.

I agree with the sentiment here as well.

I wonder if writing a DelegatingHandler to create a fake /.well-known/oauth-protected-resource document might be a reasonable workaround.

It seems reasonable to me based on the notion that alternatives such as: making the optionality of the resource parameter predicated on the availability of the PRM document - is adding too much complexity for little gains.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a more surgical fix main...FICTURE7:csharp-sdk:fallback-2. A delegating handler would offer more flexibility than this approach.

This patch adds `ProtectedResourceMetadataResponseDelegate` on
`ClientOAuthOptions` which enables consumers to inspect and modify
the response of the PRM request.

This new delegate is also called when a PRM is not found, allowing
consumers to supply one. This is useful for MCP servers which are
not compliant with the latest MCP spec. For example Atlassian's MCP
server that currently does not provide a PRM.
@FICTURE7 FICTURE7 changed the title Add legacy authorization URL fallback Add protected resource metadata response inspect & modify support Feb 13, 2026
@FICTURE7
Copy link
Author

FICTURE7 commented Feb 13, 2026

@halter73, I've pushed a new change set that adds a ProtectedResourceMetadataResponseDelegate (a bit of a mouthful but hey, its accurate) to ClientOAuthOptions that gets called when the PRM is fetched just before the nullness validation. This allows consumers to both inspect or modify the PRM.

I tried to avoid making changes the default behavior. It was not clear to me if we wanted a new default behavior similar to main...FICTURE7:csharp-sdk:fallback-2.

Copy link
Contributor

@halter73 halter73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the feedback. I think adding a Delegate to intercept the PRM handling is reasonable. @mikekistler Do you agree?

/// If the metadata of the protected resource could not be found, the delegate will be invoked with a <see langword="null"/> argument, allowing the consumers to supply defaults.
/// </para>
/// </remarks>
public Func<ProtectedResourceMetadata?, ProtectedResourceMetadata?>? ProtectedResourceMetadataResponseDelegate { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public Func<ProtectedResourceMetadata?, ProtectedResourceMetadata?>? ProtectedResourceMetadataResponseDelegate { get; set; }
public Func<ProtectedResourceMetadata?, ProtectedResourceMetadata?>? ProtectedResourceMetadataResponseFilter { get; set; }

I wonder if "Filter" is a better suffix than "Delegate" since it's a little more specific.

{
options.ResourceMetadata = new ProtectedResourceMetadata
{
Resource = resourceIncorrect,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to have an additional test to verify this works when the server returns a 404 like Atlassian rather than an invalid PRM. I think this should work. We could add a bool property to the TestOAuthServer, that if set, will cause it to add middleware before UseAuthentication that short-circuits and returns a 404 for the PRM.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, rather than adding new middleware, it'd probably be easier to set a 404 in a custom OnResourceMetadataRequest callback where you also call context.HandleResponse() to prevent the default behavior. For example:

options.Events.OnResourceMetadataRequest = async context =>
{
context.HandleResponse();
var metadata = new ProtectedResourceMetadata
{
AuthorizationServers = { new Uri(OAuthServerUrl) },
ScopesSupported = ["mcp:tools"],
};
await Results.Json(metadata, McpJsonUtilities.DefaultOptions).ExecuteAsync(context.HttpContext);
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants